/*
 * Android Runner is a multiplayer GPS game fully written by Xurxo Mendez Perez
 * 
 * Copyright (C) 2009 Xurxo Mendez Perez
 *   
 * This file is part of Android Runner.
 * 
 * Android Runner is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Android Runner is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Android Runner.  If not, see <http://www.gnu.org/licenses/>.
 */

package es.sonxurxo.androidrunner.model.service.player;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.Authentication;
import org.springframework.security.AuthenticationException;
import org.springframework.security.BadCredentialsException;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import es.sonxurxo.androidrunner.model.persistence.game.accessor.FinishedGameRegisterAccessor;
import es.sonxurxo.androidrunner.model.persistence.game.entity.FinishedGameRegister;
import es.sonxurxo.androidrunner.model.persistence.player.accessor.PlayerAccessor;
import es.sonxurxo.androidrunner.model.persistence.player.entity.Player;
import es.sonxurxo.androidrunner.model.persistence.util.exceptions.DuplicateInstanceException;
import es.sonxurxo.androidrunner.model.persistence.util.exceptions.InstanceNotFoundException;
import es.sonxurxo.androidrunner.model.service.player.to.FinishedGameRankingTO;
import es.sonxurxo.androidrunner.model.service.player.to.LoginResultTO;
import es.sonxurxo.androidrunner.model.service.player.to.PlayerHistoryTO;
import es.sonxurxo.androidrunner.model.service.player.to.PlayerRegisterTO;
import es.sonxurxo.androidrunner.model.service.player.to.PublicPlayerProfileTO;
import es.sonxurxo.androidrunner.model.service.player.to.UpdatePersonalInfoTO;
import es.sonxurxo.androidrunner.model.service.player.util.PasswordEncrypter;
import es.sonxurxo.androidrunner.model.service.player.util.exceptions.IncorrectPasswordException;

/**
 * 
 * @author "Xurxo Mendez Perez"
 * 
 */
@Service
public class PlayerServiceImpl implements PlayerService {

	@Autowired
	private PlayerAccessor playerAccessor;

	@Autowired
	private FinishedGameRegisterAccessor finishedGameRegisterAccessor;

	@Transactional(readOnly = true)
	public PublicPlayerProfileTO getPublicPlayerProfile(String login)
			throws InstanceNotFoundException {
		Player player = this.playerAccessor.findByLogin(login);
		return new PublicPlayerProfileTO(player);
	}

	@Transactional
	public boolean register(PlayerRegisterTO playerRegisterTO)
			throws DuplicateInstanceException {

		String encryptedPassword = PasswordEncrypter.crypt(playerRegisterTO
				.getClearPassword());
		Player player = new Player(playerRegisterTO.getLogin(),
				encryptedPassword, playerRegisterTO.getEmail(),
				playerRegisterTO.isShowPersonalInfo(), playerRegisterTO
						.getPhone(), playerRegisterTO.getName(),
				playerRegisterTO.getSurname(), playerRegisterTO.getCountry(),
				playerRegisterTO.getImageId(), "PLAYER");

		try {
			this.playerAccessor.findByLogin(playerRegisterTO.getLogin());
		} catch (InstanceNotFoundException e) {
			this.playerAccessor.create(player);
			return true;
		}
		throw new DuplicateInstanceException("Login exists", Player.class, "");
	}

	// ////////////////////////////////////////////////////////
	// //
	// OPTION 1: getting all registers through DAO //
	// //
	// ////////////////////////////////////////////////////////
	@Transactional
	public PlayerHistoryTO getHistoryOfGames(String login)
			throws InstanceNotFoundException {

		Player player = this.playerAccessor.findByLogin(login);

		// Gets all registers of every game played by player
		List<FinishedGameRegister> finishedGameRegisterPlayerList = this.finishedGameRegisterAccessor
				.findAllRegistersByPlayer(player);

		if (finishedGameRegisterPlayerList.size() == 0)
			return new PlayerHistoryTO();

		PlayerHistoryTO playerHistoryTO = new PlayerHistoryTO();
		FinishedGameRankingTO finishedGameRankingTO = null;

		long lastFinishedGameId = -1;

		// For each register in list
		for (FinishedGameRegister finishedGameRegisterPlayer : finishedGameRegisterPlayerList) {

			long currentFinishedGameId = finishedGameRegisterPlayer.getGame()
					.getGameId();
			// Another player entry for the same game
			if (lastFinishedGameId == currentFinishedGameId) {
				finishedGameRankingTO.addLine(finishedGameRegisterPlayer
						.getPlayer().getUserData().getLogin(),
						finishedGameRegisterPlayer.getPoints(),
						finishedGameRegisterPlayer.getNumberOfItems(),
						finishedGameRegisterPlayer.getPosition());
			}
			// Register of a new game => add ranking to history and
			// create a new ranking for current game
			else {
				if (finishedGameRankingTO != null)
					playerHistoryTO.getGames().add(finishedGameRankingTO);

				finishedGameRankingTO = new FinishedGameRankingTO(
						finishedGameRegisterPlayer.getGame().getGameId());
				finishedGameRankingTO.setCity(finishedGameRegisterPlayer
						.getGame().getCity());
				finishedGameRankingTO.setStartDate(finishedGameRegisterPlayer
						.getGame().getStartDate());
				finishedGameRankingTO.addLine(finishedGameRegisterPlayer
						.getPlayer().getUserData().getLogin(),
						finishedGameRegisterPlayer.getPoints(),
						finishedGameRegisterPlayer.getNumberOfItems(),
						finishedGameRegisterPlayer.getPosition());
			}
			lastFinishedGameId = currentFinishedGameId;
		}
		playerHistoryTO.getGames().add(finishedGameRankingTO);
		return playerHistoryTO;
	}

	// ////////////////////////////////////////////////////////
	// //
	// OPTION 2: programmed filtering (nested loop) //
	// //
	// ////////////////////////////////////////////////////////
	// @Transactional
	// public PlayerHistoryTO getHistoryOfGames(String login)
	// throws InstanceNotFoundException {
	//		
	// Player player = playerDAO.findByLogin(login);
	//
	//		
	// List<FinishedGameRegister> finishedGameRegisterPlayerList =
	// finishedGameRegisterDAO.findByPlayer(player);
	//		
	// PlayerHistoryTO playerHistoryTO = new PlayerHistoryTO();
	//		
	//		
	//		
	// // For each game played by this player
	// for (FinishedGameRegister finishedGameRegisterPlayer
	// :finishedGameRegisterPlayerList) {
	//			
	// FinishedGame finishedGame =
	// finishedGameRegisterPlayer.getFinishedGame();
	//			
	// // Takes the registers of that game
	// List<FinishedGameRegister> finishedGameRegisterGameList =
	// finishedGameRegisterDAO.findByFinishedGame(finishedGame);
	//			
	// FinishedGameRankingTO finishedGameRankingTO =
	// new FinishedGameRankingTO();
	// finishedGameRankingTO.setGameId(finishedGame.getGameId());
	// // Adds lines to the game ranking
	// for (FinishedGameRegister finishedGameRegisterGame
	// :finishedGameRegisterGameList) {
	// finishedGameRankingTO.addLine(
	// finishedGameRegisterGame.getPlayer().getLogin(),
	// finishedGameRegisterGame.getPoints());
	// }
	//			
	// // Adds the game ranking to the history of the player
	// playerHistoryTO.getGames().add(finishedGameRankingTO);
	// }
	// return playerHistoryTO;
	// }

	@Transactional
	public boolean updatePersonalInfo(String login,
			UpdatePersonalInfoTO updatePersonalInfoTO)
			throws InstanceNotFoundException {

		Player player = this.playerAccessor.findByLogin(login);
		player.getUserData().setPhone(updatePersonalInfoTO.getPhone());
		player.getUserData().setEmail(updatePersonalInfoTO.getEmail());
		player.getUserData().setShowPersonalInfo(
				updatePersonalInfoTO.isShowPersonalInfo());
		player.getUserData().setName(updatePersonalInfoTO.getName());
		player.getUserData().setSurname(updatePersonalInfoTO.getSurname());
		player.getUserData().setCountry(updatePersonalInfoTO.getCountry());
		player.setImageId(updatePersonalInfoTO.getImageId());
		this.playerAccessor.update(player);

		return true;
	}

	@Transactional
	public boolean changePassword(String login, String oldClearPassword,
			String newClearPassword) throws InstanceNotFoundException,
			IncorrectPasswordException {

		Player player = this.playerAccessor.findByLogin(login);
		if (PasswordEncrypter.isClearPasswordCorrect(oldClearPassword, player
				.getUserData().getEncryptedPassword())) {

			String encryptedPassword = PasswordEncrypter
					.crypt(newClearPassword);
			player.getUserData().setEncryptedPassword(encryptedPassword);

			this.playerAccessor.update(player);

			return true;
		}
		throw new IncorrectPasswordException(login);
	}

	public FinishedGameRegisterAccessor getFinishedGameRegisterAccessor() {
		return this.finishedGameRegisterAccessor;
	}

	public void setFinishedGameRegisterAccessor(
			FinishedGameRegisterAccessor finishedGameRegisterAccessor) {
		this.finishedGameRegisterAccessor = finishedGameRegisterAccessor;
	}

	public PlayerAccessor getPlayerAccessor() {
		return this.playerAccessor;
	}

	public void setPlayerAccessor(PlayerAccessor playerAccessor) {
		this.playerAccessor = playerAccessor;
	}

	@Transactional(readOnly = true)
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {

		String login = (String) authentication.getPrincipal();
		String clearPassword = (String) authentication.getCredentials();
		LoginResultTO loginResultTO = null;
		try {
			loginResultTO = this.login(login, clearPassword);
		} catch (IncorrectPasswordException e) {
			throw new BadCredentialsException(e.getLocalizedMessage());
		} catch (InstanceNotFoundException e) {
			throw new BadCredentialsException(e.getLocalizedMessage());
		}

		GrantedAuthority[] grantedAuthorities = null;
		if (loginResultTO != null) {
			// TODO : take real roles from player
			List<String> roles = new ArrayList<String>();
			roles.add(loginResultTO.getRole());
			grantedAuthorities = new GrantedAuthority[roles.size()];
			for (int i = 0; i < roles.size(); i++) {
				String role = roles.get(i);
				GrantedAuthority grantedAuthority = new GrantedAuthorityImpl(
						role);
				grantedAuthorities[i] = grantedAuthority;
			}
		} else {
			throw new BadCredentialsException("Invalid login/password");
		}
		return new UsernamePasswordAuthenticationToken(loginResultTO,
				authentication.getCredentials(), grantedAuthorities);
	}

	private LoginResultTO login(String login, String clearPassword)
			throws InstanceNotFoundException, IncorrectPasswordException {

		Player player = this.playerAccessor.findByLogin(login);

		if (PasswordEncrypter.isClearPasswordCorrect(clearPassword, player
				.getUserData().getEncryptedPassword()))
			return new LoginResultTO(player);

		throw new IncorrectPasswordException(login);
	}

	public List<PublicPlayerProfileTO> getOrderedByPoints(int startIndex,
			int count) {
		List<Player> players = this.playerAccessor.findOrderedByPoints(startIndex, count);
		List<PublicPlayerProfileTO> result = new ArrayList<PublicPlayerProfileTO>();
		for (Player player : players) {
			result.add(new PublicPlayerProfileTO(player));
		}
		return result;
	}

}
